<?php

namespace App\Http\Common;
use App\Models\File;
use Qcloud\Cos\Client;
use Qcloud\Cos\Exception;
use RuntimeException;

/**
 * 自定义的全局的文件上传以及下载的类 文件工具类
 * 使用单列模式 通过 UploadFiles::getInstance()获得
 * @author Administrator
 *
 */
class FilesTools
{
    //创建静态私有的变量保存该类对象
    private static $instance;
    //参数
    private $config;
    //防止直接创建对象
    private function __construct($config){
        $this->config = $config;
    }
    //防止克隆对象
    private function __clone(){
    }
    // 使用单列模式
    public static function getInstance($config=null){
        //判断$instance是否是Uni的对象
        //没有则创建
        if (!self::$instance instanceof self) {
            self::$instance = new self($config);
        }
        return self::$instance;
    }
    /**
     * 上传文件 的 接口
     * @param $request
     * @param string $type
     * @param string $table_name
     * @return string
     */
    public function uploadFile($request,$type="",$table_name=""){
        try{
            $all = $request->request->all();
            if(!$type) $type = MyUtils::sg($all,"type","1");
            if(!$table_name) $table_name = MyUtils::sg($all,"table_name","wy_rooms");
            // 文件，主要有前端传递过来的 上传的文件对象
            $file = $request->files;
            if (!empty($file)) {
                // 拿到文件对象
                $files = $_FILES['file'];
                // 上传文件的名称
                $name = pathinfo($files['name'])['filename'];
                // 文件大小
                $filesize = $files['size'];
                // 上传到本地
                $myfile = $this->uploadSingleToLocal($files);
                // 新增 数据库 一条数据
                $fileInfo['local_path'] = $myfile['filename'];//文件路径
                $fileInfo['type'] = $type;//类型
                $fileInfo['name'] = $myfile['filename'];//文件名称
                $fileInfo['table_name'] = $table_name;//表的名称
                $fileInfo['filesize'] = $filesize;
                $wyFileId = File::insertGetId($fileInfo);
                // 上传到腾讯 空间
                $res = $this->tencentFileInitAndAction("tencentFileUpload",$myfile['filename'],$myfile['filepath']);
                if ($res->get("ObjectURL")) {// 上传成功
                    $url = $res->get("ObjectURL");
                    // 将文件路径 信息保存到数据库中
                    $fileInfo['remote_path'] = $url;
                    // 更新数据库中  远程 的文件 路径
                    File::where("id",$wyFileId)->update($fileInfo);
                    // 将本地的文件路径以及 远程的文件的路径 传递给 前端
                    return MyUtils::dealFD([
                        "local_path"=>$myfile['filename'],
                        "remote_path"=>$url,
                    ],1);
                }else {// 上传失败
                    $message = MyUtils::sg($res,"message");
                    return MyUtils::dealFD($message,-1);
                }
            }else{
                return MyUtils::dealFD("上传文件失败",-1);
            }
        }catch (\Exception $e){
            return MyUtils::dealFD("服务器错误",-1);
        }
    }

    /**
     * 上传本地 文件到 远程
     * @param $url 需要下载的远程的网站
     * @return string
     */
    public function uploadUrlFileToTencent($url){
        $content = HttpRequest::getInstance()->get($url);
        $generateFilePath = '/uploads/qrCode/';//生成文件;
        $temp = time().MyUtils::randName(10);
        $generateFileName = "business_qrCode$temp.png";//生成文件;
        if(!file_exists($generateFilePath)){
            MyUtils::mkdirs($generateFilePath);
        };
        $filePath = storage_path($generateFilePath.$generateFileName);
        file_put_contents($filePath,$content);
        $files['name'] = $generateFileName;
        $files['tmp_name'] = $filePath;
        $files['size'] = filesize($filePath);
        $files_['files'] = $files;
        $res = FilesTools::getInstance()->uploadFilePlus($files_,'','',false);
        $res = json_decode($res);
        $remote_path = "";
        if ($res->code==1){// 上传到腾讯云成功
            // 移除本地的文件
            unlink($files['tmp_name']);
            $data = $res->data;
            $remote_path = $data->remote_path;
        }
        return $remote_path;
    }

    /**
     * 上传文件 的 接口 上传头像等接口
     * @param $request
     * @param string $type
     * @param string $table_name
     * @param bool $uploadLocal
     * @return string
     */
    public function uploadFile2($request,$type="",$table_name="",$uploadLocal = true){
        try{
            $all = $request->request->all();
            if(!$type) $type = MyUtils::sg($all,"type","2");
            if(!$table_name) $table_name = MyUtils::sg($all,"table_name","wy_rooms");
            // 文件，主要有前端传递过来的 上传的文件对象
            $file = $request->files;
            if (!empty($file)) {
                // 拿到文件对象
                $files = $_FILES['files'];
                // 上传文件的名称
                $name = pathinfo($files['name'])['filename'];
                // 文件大小
                $filesize = $files['size'];
                // 上传到本地
                if ($uploadLocal){
                    $myfile = $this->uploadSingleToLocal($files);
                }else{
                    $myfile = ['filepath'=>$files['tmp_name'],'filename'=>$name];
                }
                // 新增 数据库 一条数据gleToL
                $fileInfo['local_path'] = $myfile['filename'];//文件路径
                $fileInfo['type'] = $type;//类型
                $fileInfo['name'] = $myfile['filename'];//文件名称
                $fileInfo['table_name'] = $table_name;//表的名称
                $fileInfo['filesize'] = $filesize;
                $wyFileId = WyFile::insertGetId($fileInfo);
                // 上传到腾讯 空间
                $res = $this->tencentFileInitAndAction("tencentFileUpload",$myfile['filename'],$myfile['filepath']);
                if ($res && $res->get("ObjectURL")) {// 上传成功
                    $url = $res->get("ObjectURL");
                    // 将文件路径 信息保存到数据库中
                    $fileInfo['remote_path'] = $url;
                    // 更新数据库中  远程 的文件 路径
                    WyFile::where("id",$wyFileId)->update($fileInfo);
                    // 将本地的文件路径以及 远程的文件的路径 传递给 前端
                    return MyUtils::dealFD([
                        "remote_path"=>$url,
                    ],1);
                }else {// 上传失败
                    $message = MyUtils::sg($res,"message");
                    return MyUtils::dealFD($message,-1);
                }
            }else{
                return MyUtils::dealFD("上传文件失败",-1);
            }
        }catch (\Exception $e){
            return MyUtils::dealFD("服务器错误".json_encode($e),-1);
        }
    }

    /**
     * 上传文件 的 接口 上传本地的文件，并非前端上传的文件
     * @param $params
     * @param string $type
     * @param string $table_name
     * @param bool $uploadLocal
     * @return string
     */
    public function uploadFilePlus($params,$type="",$table_name="",$uploadLocal = true){
        try{
            $all = $params;
            if(!$type) $type = MyUtils::sg($all,"type","2");
            if(!$table_name) $table_name = MyUtils::sg($all,"table_name","wy_rooms");
            // 文件，主要有前端传递过来的 上传的文件对象
            $file = $params['files'];
            if (!empty($file)) {
                // 拿到文件对象
                $files = $params['files'];
                // 上传文件的名称
                $name = pathinfo($files['name'])['filename'];
                // 文件大小
                $filesize = $files['size'];
                // 上传到本地
                if ($uploadLocal){
                    $myfile = $this->uploadSingleToLocal($files);
                }else{
                    $myfile = ['filepath'=>$files['tmp_name'],'filename'=>$name];
                }
                // 新增 数据库 一条数据gleToL
                $fileInfo['local_path'] = $myfile['filename'];//文件路径
                $fileInfo['type'] = $type;//类型
                $fileInfo['name'] = $myfile['filename'];//文件名称
                $fileInfo['table_name'] = $table_name;//表的名称
                $fileInfo['filesize'] = $filesize;
                $wyFileId = WyFile::insertGetId($fileInfo);
                // 上传到腾讯 空间
                $res = $this->tencentFileInitAndAction("tencentFileUpload",$myfile['filename'],$myfile['filepath']);
                if ($res->get("ObjectURL")) {// 上传成功
                    $url = $res->get("ObjectURL");
                    // 将文件路径 信息保存到数据库中
                    $fileInfo['remote_path'] = $url;
                    // 更新数据库中  远程 的文件 路径
                    WyFile::where("id",$wyFileId)->update($fileInfo);
                    // 将本地的文件路径以及 远程的文件的路径 传递给 前端
                    return MyUtils::dealFD([
                        "remote_path"=>$url,
                    ],1);
                }else {// 上传失败
                    $message = MyUtils::sg($res,"message");
                    return MyUtils::dealFD($message,-1);
                }
            }else{
                return MyUtils::dealFD("上传文件失败",-1);
            }
        }catch (\Exception $e){
            return MyUtils::dealFD("服务器错误".json_encode($e),-1);
        }
    }

    /**
     * 小程序的上传文件到云端 比如头像等信息
     * @param $request
     * @param string $type
     * @param string $table_name
     * @return string
     */
    public function uploadFrontFile($request,$type="",$table_name=""){
        try{
            $all = $request->request->all();
            if(!$type) $type = MyUtils::sg($all,"type","3");
            if(!$table_name) $table_name = MyUtils::sg($all,"table_name","wy_rooms");
            // 文件，主要有前端传递过来的 上传的文件对象
            $file = $request->files;
            if (!empty($file)) {
                // 拿到文件对象
                $files = $_FILES['files'];
                // 上传文件的名称
                $name = pathinfo($files['name'])['filename'];
                // 文件大小
                $filesize = $files['size'];
                // 上传到本地
                $myfile = $this->uploadSingleToLocal($files);
                // 新增 数据库 一条数据gleToL
                $fileInfo['local_path'] = $myfile['filename'];//文件路径
                $fileInfo['type'] = $type;//类型
                $fileInfo['name'] = $myfile['filename'];//文件名称
                $fileInfo['table_name'] = $table_name;//表的名称
                $fileInfo['filesize'] = $filesize;
                $wyFileId = WyFile::insertGetId($fileInfo);
                // 上传到腾讯 空间
                $res = $this->tencentFileInitAndAction("tencentFileUpload",$myfile['filename'],$myfile['filepath']);
                if ($res->get("ObjectURL")) {// 上传成功
                    $url = $res->get("ObjectURL");
                    // 将文件路径 信息保存到数据库中
                    $fileInfo['remote_path'] = $url;
                    // 更新数据库中  远程 的文件 路径
                    WyFile::where("id",$wyFileId)->update($fileInfo);
                    // 将本地的文件路径以及 远程的文件的路径 传递给 前端
                    return MyUtils::dealFD([
                        "remote_path"=>$url,
                    ],1);
                }else {// 上传失败
                    $message = MyUtils::sg($res,"message");
                    return MyUtils::dealFD($message,-1);
                }
            }else{
                return MyUtils::dealFD("上传文件失败",-1);
            }
        }catch (\Exception $e){
            return MyUtils::dealFD("服务器错误".json_encode($e),-1);
        }
    }
    /**
     * 上传单个文件到本地，并验证上传的文件里面的规则
     * @return boolean
     */
    public function uploadSingleToLocal($file)
    {
        //上传文件
        //项目在服务器的绝对根路径
        $basePath =base_path();
        // 拿到年月的时间
        $time = time();
        $timePath = date('y/md/', $time);
        $temppath = '/public/uploads/yuyin/' . $timePath;
        $filePath = $basePath . '/' .$temppath;
        // 通过时间年月来创建文件夹
        MyUtils::mkdirs($filePath);
        $randomName = $time.MyUtils::randName();
        $name =  $randomName . '.' . pathinfo($file['name'])['extension'];
        $fileName = $filePath . $name;
        $myfile = false;
        if (!file_exists($fileName)) { // 文件不存在就新建文件然后进行写入操作
            $myfile = move_uploaded_file($file["tmp_name"],$fileName);
        }
        if ($myfile){
            return ['filepath'=>$fileName,'filename'=>$temppath.$name];
        }else{
            return $myfile;
        }
    }

    /**
     * 上传单个文件到 腾讯 云 （对象存储），并验证上传的文件里面的规则
     * @return boolean
     */
    public function uploadSingleToTencent($file)
    {

    }

    /**
     * 初始化腾讯的 云 API 密钥 SecretId 云 API 密钥 SecretKey等信息，并且设置一个默认的存储桶地域
     * 然后实行下载操作 以及查询 上传等操作，$method就是所需要实现的方法 ，比如 下载 上传 查询等操作
     * @params $key 指定对象在 COS 上的对象键
     * @params $other 只是 路径 文件的路径
     * @param $method
     * @param string $key
     * @param $other
     * @return
     */
    public function tencentFileInitAndAction($method,$key="",$other){
        $secretId = env('TENCENT_SECRETID'); //"云 API 密钥 SecretId";
        $secretKey = env('TENCENT_SECRETKEY'); //"云 API 密钥 SecretKey";
        $bucket = env('TENCENT_BUCKET');
        $folder = env('TENCENT_FOLDER');
        $region = env('TENCENT_REGION');//设置一个默认的存储桶地域
        $cosClient = new Client(
            array(
                'region' => $region,
                'schema' => 'http', //协议头部，默认为http
                'credentials'=> array(
                    'secretId'  => $secretId ,
                    'secretKey' => $secretKey
                )
            )
        );
        return $this->$method($cosClient,$bucket,$key,$other);
    }
    /**
     * 创建存储桶
     */
    public function tencentFileCreateBucketList($cosClient,$bucket,$key){
        try {
            $result = $cosClient->putObject(array(
                'Bucket' => $bucket,
                'Key' => $key,
                'Body' => "Hello World!\n"
            ));
            # 可以直接通过$result读出返回结果
            return $result['ETag'];
        } catch (Exception\ServiceResponseException $e) {
            echo "ZZZ\n";
            echo $e->getMessage();
            echo $e->getCosRequestId();
        } catch (RuntimeException $e) {
            echo "###\n";
            echo $e->getMessage() . "\n";
        } catch (\Exception $e) {
            echo "111\n";
            echo "222\n";
        }
    }
    /**
     * 查询存储桶列表
     */
    public function tencentFileGetBucketList($cosClient,$bucket,$key){
        try {
            //请求成功
            $result = $cosClient->listBuckets();
            return $result;
        } catch (\Exception $e) {
            //请求失败
            echo($e);
        }
    }
    /**
     * 单次调用listObjects接口一次只能查询1000个对象，如需要查询所有的对象，则需要循环调用
     * @params $bucket 存储桶名称
     * @params $prefix 列出对象的前缀
     * @params $marker 上次列出对象的断点
     */
    public function tencentFileGetBucketAllList($cosClient,$bucket,$prefix,$marker){
        try {
            while (true) {
                $result = $cosClient->listObjects(array(
                    'Bucket' => $bucket,
                    'Marker' => $marker,
                    'MaxKeys' => 1000 // 设置单次查询打印的最大数量，最大为1000
                ));
                foreach ($result['Contents'] as $rt) {
                    // 打印key
                    echo($rt['Key'] . "\n");
                }
                $marker = $result['NextMarker']; // 设置新的断点
                if (!$result['IsTruncated']) {
                    break; // 判断是否已经查询完
                }
            }
        } catch (\Exception $e) {
            echo($e);
        }
    }
/*********************************************上传接口*******************************************/
    /**
     * 上传对象  Body
     * 使用 putObject 接口上传文件（最大5G）。
     * 使用 Upload 接口分块上传文件。
     * @params $cosClient
     * @params $bucket
     * @params $key
     * @params $Body body可以是字符串
     */
    public function tencentFileUploadBody($cosClient,$bucket,$key,$Body)
    {
        ## putObject(上传接口，最大支持上传5G文件)
        ### 上传内存中的字符串
        try {
            $result = $cosClient->putObject(array(
                'Bucket' => $bucket,
                'Key' => $key,
                'Body' => $Body));
            print_r($result);
        } catch (\Exception $e) {
            echo "$e\n";
        }
    }
    /**
     * 上传文件流
     * 使用 putObject 接口上传文件（最大5G）。
     * 使用 Upload 接口分块上传文件。
     * @params $cosClient
     * @params $bucket
     * @params $key
     * @params $srcPath 本地文件绝对路径
     */
    public function tencentFileUpload($cosClient,$bucket,$key,$srcPath)
    {
        ### 上传文件流
        try {
            $result = $cosClient->putObject(array(
                'Bucket' => $bucket,
                'Key' => $key,
                'Body' => fopen($srcPath, 'rb')));
            return $result;
        } catch (\Exception $e) {
            dd("$e\n");
        }
    }
    /**
     * 设置header和meta
     * 使用 putObject 接口上传文件（最大5G）。
     * 使用 Upload 接口分块上传文件。
     * @params $cosClient
     * @params $bucket
     * @params $key
     * @params $srcPath 本地文件绝对路径
     */
    public function tencentFileUploadHeader($cosClient,$bucket,$key,$srcPath)
    {
        ### 设置header和meta
        try {
            $result = $cosClient->putObject(array(
                'Bucket' => $bucket,
                'Key' => $key,
                'Body' => fopen($srcPath, 'rb'),
                'ACL' => 'string',
                'CacheControl' => 'string',
                'ContentDisposition' => 'string',
                'ContentEncoding' => 'string',
                'ContentLanguage' => 'string',
                'ContentLength' => filesize ( $srcPath ),
                'ContentType' => 'string',
                'Expires' => 'mixed type: string (date format)|int (unix timestamp)|\DateTime',
                'GrantFullControl' => 'string',
                'GrantRead' => 'string',
                'GrantWrite' => 'string',
                'Metadata' => array(
                    'string' => 'string',
                ),
                'StorageClass' => 'string'));
            return $result;
        } catch (\Exception $e) {
            echo "$e\n";
        }
    }
    /**
     * 高级上传接口，默认使用分块上传最大支持50T
     * 使用 putObject 接口上传文件（最大5G）。
     * 使用 Upload 接口分块上传文件。
     * @params $cosClient
     * @params $bucket
     * @params $key
     * @params $body body的字符串
     */
    public function tencentFileUploadAdvanceBody($cosClient,$bucket,$key,$body)
    {
        ## Upload(高级上传接口，默认使用分块上传最大支持50T)
        ### 上传内存中的字符串
        try {
            $result = $cosClient->Upload(
                $bucket = $bucket,
                $key = $key,
                $body = $body);
            return $result;
        } catch (\Exception $e) {
            echo "$e\n";
        }
    }
    /**
     * 高级上传接口，默认使用分块上传最大支持50T
     * 使用 putObject 接口上传文件（最大5G）。
     * 使用 Upload 接口分块上传文件。
     * @params $cosClient
     * @params $bucket
     * @params $key
     * @params $srcPath 本地文件绝对路径
     */
    public function tencentFileUploadAdvance($cosClient,$bucket,$key,$srcPath)
    {
        ### 上传文件流
        try {
            $result = $cosClient->Upload(
                $bucket = $bucket,
                $key = $key,
                $body = fopen($srcPath, 'rb'));
            return $result;
        } catch (\Exception $e) {
            echo "$e\n";
        }
    }
    /**
     * 高级上传接口，默认使用分块上传最大支持50T
     * 使用 putObject 接口上传文件（最大5G）。
     * 使用 Upload 接口分块上传文件。
     * @params $cosClient
     * @params $bucket
     * @params $key
     * @params $srcPath 本地文件绝对路径
     */
    public function tencentFileUploadAdvanceHeader($cosClient,$bucket,$key,$srcPath)
    {
        ### 设置header和meta
        try {
            $result = $cosClient->Upload(
            $bucket= $bucket,
            $key = $key,
            $body = fopen($srcPath, 'rb'),
            $options = array(
                'ACL' => 'string',
                'CacheControl' => 'string',
                'ContentDisposition' => 'string',
                'ContentEncoding' => 'string',
                'ContentLanguage' => 'string',
                'ContentLength' => filesize ( $srcPath ),
                'ContentType' => 'string',
                'Expires' => 'mixed type: string (date format)|int (unix timestamp)|\DateTime',
                'GrantFullControl' => 'string',
                'GrantRead' => 'string',
                'GrantWrite' => 'string',
                'Metadata' => array(
                    'string' => 'string',
                ),
                'StorageClass' => 'string'));
            return $result;
        } catch (\Exception $e) {
            echo "$e\n";
        }
    }
/*********************************************下载接口*******************************************/
    /**
     * 下载接口
     * @params $cosClient
     * @params $bucket
     * @params $key
     */
    public function tencentFileDownload($cosClient,$bucket,$key)
    {
        ## getObject(下载文件)
        ### 下载到内存
        try {
            $result = $cosClient->getObject(array(
                'Bucket' => $bucket,
                'Key' => $key));
            // 请求成功
            return $result['Body'];
        } catch (\Exception $e) {
            // 请求失败
            echo "$e\n";
        }
    }
    /**
     * 下载到本地
     * @params $cosClient
     * @params $bucket 存储桶，格式：BucketName-APPID
     * @params $key 对象在存储桶中的位置，即称对象键
     * @params $localPath 下载到本地指定路径
     */
    public function tencentFileDownloadLocal($cosClient,$bucket,$key,$localPath)
    {
        ### 下载到本地
        try {
            $result = $cosClient->getObject(array(
                'Bucket' => $bucket,
                'Key' => $key,
                'SaveAs' => $localPath));
            return $result;
        } catch (\Exception $e) {
            // 请求失败
            echo "$e\n";
        }
    }
    /**
     * 指定下载范围
     * @params $cosClient
     * @params $bucket 存储桶，格式：BucketName-APPID
     * @params $key 对象在存储桶中的位置，即称对象键
     * @params $localPath 下载到本地指定路径
     */
    public function tencentFileDownloadLocalArrange($cosClient,$bucket,$key,$localPath)
    {
        /*
         * Range 字段格式为 'bytes=a-b'
         */
        try {
            $localPath = @$localPath;//下载到本地指定路径
            $result = $cosClient->getObject(array(
                'Bucket' => $bucket,
                'Key' => $key,
                'Range' => 'bytes=0-10',
                'SaveAs' => $localPath));
            return $result;
        } catch (\Exception $e) {
            // 请求失败
            echo "$e\n";
        }
    }
    /**
     * 设置返回header
     * @params $cosClient
     * @params $bucket 存储桶，格式：BucketName-APPID
     * @params $key 对象在存储桶中的位置，即称对象键
     * @params $localPath 下载到本地指定路径
     */
    public function tencentFileSetBackHeader($cosClient,$bucket,$key,$localPath)
    {
        try {
            $localPath = @$localPath;//下载到本地指定路径
            $result = $cosClient->getObject(array(
                'Bucket' => $bucket,
                'Key' => $key,
                'ResponseCacheControl' => 'string',
                'ResponseContentDisposition' => 'string',
                'ResponseContentEncoding' => 'string',
                'ResponseContentLanguage' => 'string',
                'ResponseContentType' => 'string',
                'ResponseExpires' => 'mixed type: string (date format)|int (unix timestamp)|\DateTime',
                'SaveAs' => $localPath));
            return $result;
            } catch (\Exception $e) {
                // 请求失败
                echo "$e\n";
            }
    }
    /**
     * 获取文件UrL
     * @params $cosClient
     * @params $bucket 存储桶，格式：BucketName-APPID
     * @params $key 对象在存储桶中的位置，即称对象键
     * @params $localPath 下载到本地指定路径
     */
    public function tencentFileGetUrl($cosClient,$bucket,$key)
    {
        try {
            $signedUrl = $cosClient->getObjectUrl($bucket, $key, '+10 minutes');
            // 请求成功
            return $signedUrl;
        } catch (\Exception $e) {
            // 请求失败
            print_r($e);
        }
    }
/****************************************删除接口*******************************************/
    /**
     * 删除文件
     * @params $cosClient
     * @params $bucket 存储桶，格式：BucketName-APPID
     * @params $key 对象在存储桶中的位置，即称对象键
     */
    public function tencentFileDelete($cosClient,$bucket,$key)
    {
        try {
            $result = $cosClient->deleteObject(array(
                'Bucket' => $bucket,
                'Key' => $key,
                'VersionId' => 'string'
            ));
            // 请求成功
            return $result;
        } catch (\Exception $e) {
            // 请求失败
            echo($e);
        }
    }
    /**
     * 删除多个文件
     * @params $cosClient
     * @params $bucket 存储桶，格式：BucketName-APPID
     * @params $key 数组 对象在存储桶中的位置，即称对象键 ,可以是多个
     * $key 结构应该为
     * array(
            array(
            'Key' => $key1,
            ),
            array(
            'Key' => $key2,
            ),
            ...
        ),
     */
    public function tencentFileDeleteMulti($cosClient,$bucket,$keys)
    {
        try {
            $result = $cosClient->deleteObjects(array(
                'Bucket' => $bucket,
                'Objects' => $keys,
            ));
            // 请求成功
            return $result;
        } catch (\Exception $e) {
            // 请求失败
            echo($e);
        }
    }
}
